Skip to content

Automate repository conventions and stage static analysis enforcement - #16025

Open
jamesfredley wants to merge 4 commits into
8.0.xfrom
chore/automate-project-conventions
Open

Automate repository conventions and stage static analysis enforcement#16025
jamesfredley wants to merge 4 commits into
8.0.xfrom
chore/automate-project-conventions

Conversation

@jamesfredley

@jamesfredley jamesfredley commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

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-core

Each module opts in through grailsCodeAnalysis { pmdEnabled = true }. GrailsCodeAnalysisExtension now exposes lazy PMD and SpotBugs properties, and plugin configuration is deferred to afterEvaluate. The existing -P flags 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-based dependsOn and mustRunAfter. 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 through aggregateStyleViolations rather than aggregateViolations, the codestyle.yml core job now invokes validateRepositoryConventions explicitly and publishes its report. --continue is required to get reports when an analyzer fails, which the documented commands and every workflow already pass.

Repository conventions

./gradlew validateRepositoryConventions writes build/reports/violations/REPOSITORY_CONVENTIONS.md and is included through aggregateViolations. It validates:

  • Skill front matter at line 1, with string name, description, and license fields, matching valid directory names and unique names.
  • Every skill path referenced by AGENTS.md exists. It does not require AGENTS.md to be a complete skill index.
  • Third-party GitHub Actions use a consistent lowercase 40-hex immutable reference across workflows and local composite actions. actions/* and apache/*, including apache/grails-github-actions, may use version or branch references but must retain a non-empty @ref.
  • Docker actions and workflow job or service images use literal name@sha256:<digest> values. Expressions receive a specific failure because their immutability cannot be verified.
  • Duplicate message keys across **/grails-app/i18n/**/*.properties, including escaped separators and continuation rules.
  • YAML parsing, recursive local-action manifests, and RAT provenance.

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.md references, 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.yml remain 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-party apache/* actions introduced by this PR were reverted. gradle.yml, vulnerability-scan.yml, release-close.yml, and release-publish-docs.yml have no diff from 8.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 only SPOTBUGS_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.md are symlinks to files that ship in the source ZIP. The release ZIP again stores those symlinks with zip -y, includes their targets, and normalizes symlink timestamps with touch -h. Source checkout credentials remain disabled with persist-credentials: false.

RAT exclusions are limited to .omo/**, .worktrees/**, and .claude/settings.json. .asf.yaml is 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 through aggregateViolations.

SnakeYAML now uses snakeyamlVersion=2.6 from the root build-tooling properties rather than a hardcoded dependency version.

Documentation and tests

AGENTS.md and 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

Issue Remaining work Why it is not in this PR
#16014 Stabilize SpotBugs task wiring and detector execution Broad runs expose generated-output dependencies and detector failures.
#16018 Triage the SpotBugs baseline Regenerate the baseline after #16014, then fix or document findings before enforcement.
#16017 Resolve remaining root PMD findings A broad cleanup would obscure this build-policy change.
#16016 Resolve PMD findings in grails-gradle-model The independent Gradle-plugin build remains report-only until its model findings are fixed.
#16015 Resolve maintained-source PMD findings in Grails Forge and add staged CI enforcement Forge needs its own supported enforcement path.

The conflict with open PR #15977 was removed by relaxing the AGENTS.md check. Either an explicit index or directory-based discovery now passes, while every new skill must still provide string name, description, and license front matter.

Suggested next steps

  1. Resolve Stabilize SpotBugs task wiring and detector execution #16014, then regenerate and triage the SpotBugs baseline in Triage the existing SpotBugs finding baseline #16018.
  2. Add clean PMD modules incrementally while resolving Enable PMD incrementally across remaining core modules #16017.
  3. Complete the focused grails-gradle-model cleanup in Resolve existing PMD findings in grails-gradle-model #16016.
  4. Establish Forge's staged PMD enforcement under Scope and resolve PMD findings in grails-forge #16015.

Verification

  • build-logic plugin suite: from build-logic, ../gradlew :build-logic:test passes and covers every behavior described above. Result: 85 tests, 0 failures, 0 errors, 0 skipped, up from 77 on the previous revision.

  • ./gradlew rat passes 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 that gradle.yml, vulnerability-scan.yml, release-close.yml, and release-publish-docs.yml have 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

Code Quality

  • Tests were added or updated for every changed build-logic behavior.
  • The build-logic plugin 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.
  • The project code-style and aggregate violation workflows are covered by the build-logic suite.
  • This PR does not include mass reformatting or a broad legacy-analysis cleanup.

Licensing and Attribution

  • New source files contain Apache License 2.0 headers, and RAT provenance is covered.
  • The contribution is original work prepared for this repository.

Documentation

  • AGENTS.md and the violation-fixer skill document the commands, reports, policy, opt-in model, and remediation paths.
  • This build-policy change adds no Grails user-facing feature, runtime API, or upgrade requirement.
  • The description covers scope, staged boundary, verification, and follow-up work.

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
Copilot AI review requested due to automatic review settings July 20, 2026 12:38

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

codecov Bot commented Jul 20, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 19.4467%. Comparing base (dfebb9a) to head (9a8fbee).
⚠️ Report is 125 commits behind head on 8.0.x.

Additional details and impacted files

Impacted file tree graph

@@                 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     

see 1976 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@jamesfredley

Copy link
Copy Markdown
Contributor Author

@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.

@borinquenkid

Copy link
Copy Markdown
Member

@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.

@jdaugherty

Copy link
Copy Markdown
Contributor

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
@jamesfredley

Copy link
Copy Markdown
Contributor Author

@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.

@jamesfredley jamesfredley moved this to Todo in Apache Grails Jul 24, 2026
@borinquenkid borinquenkid added this to the grails:8.0.0-RC1 milestone Jul 25, 2026

@jdaugherty jdaugherty left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread gradle/rat-root-config.gradle Outdated
Comment thread .github/workflows/vulnerability-scan.yml Outdated
Comment thread .github/workflows/release.yml Outdated
Comment thread .github/workflows/release.yml Outdated
Comment thread .github/workflows/release.yml
Comment thread build-logic/plugins/build.gradle Outdated
Comment thread gradle.properties Outdated

@jdaugherty jdaugherty left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
@jamesfredley

Copy link
Copy Markdown
Contributor Author

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 reversal

Every actions/* and apache/* pin this PR introduced is reverted. gradle.yml, vulnerability-scan.yml, release-close.yml and release-publish-docs.yml now have a zero diff against 8.0.x. That also removes the actions/setup-java v4 to v5 major-version crossing you spotted inside the two release reproducibility pins, which was an unintended side effect of mechanically swapping tags for the SHAs they currently resolve to.

Rather than leave the validator encoding the opposite of the policy, RepositoryConventionsTask now exempts the actions and apache owner namespaces from the 40-hex requirement and from the cross-file SHA consistency check, while still requiring a non-empty @ref. Third-party actions and Docker digests stay enforced.

Three places I did not do exactly what was asked

  1. codeanalysis.yml upload-artifact pins. Every actions/* reference in that file is already SHA-pinned on 8.0.x; the new advisory steps copy the convention already in force in the same file. Converting only the new lines would leave two styles three lines apart, and converting the file means deciding whether setup-java lands on @v4 or @v5 there, which is exactly the silent major-version decision you flagged elsewhere. The gate no longer enforces SHAs for actions/*, so normalizing that file is unblocked as a standalone change. Happy to do it here if you would rather.

  2. release-drafter. Documented rather than re-pinned. There is now a comment on the COMMIT_SHA constant recording that a 40-hex ref may be a commit or an annotated-tag object, that both are immutable, and that the validator cannot distinguish them.

  3. The .gitignore and RAT requests. The premise turned out to be wrong on my side: CLAUDE.md, GEMINI.md, .clinerules, .cursorrules, .windsurfrules are tracked symlinks to AGENTS.md, and .claude/skills/*/SKILL.md are tracked symlinks into .agents/skills/. So they cannot be gitignored, and per your other comment they now ship in the source zip again, stored as links by zip -y. I did try dropping their RAT exclusion on the theory that RAT would follow the link to the licensed target; ./gradlew rat then failed with 8 unapproved licenses, because on a platform without symlink support git materializes each one as a plain text file containing the target path. The exclusion is kept with that written down as the reason. .gitignore gained only the genuinely local .omo/ and .worktrees/.

Two things the final review caught that your comments led to

Both 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 aggregateStyleViolations edge, as you asked, was correct for the task graph but no workflow actually invokes aggregateViolations. codestyle.yml runs aggregateStyleViolations and codeanalysis.yml runs aggregateAnalysisViolations, so the entire feature would have been unenforced on PRs. The edge stays removed; the core codestyle.yml job now invokes validateRepositoryConventions explicitly and publishes its report. There is a regression test asserting both directions so the edge cannot be quietly reintroduced.

The Modules analyzed: header alone did not fix the partial-report problem. Because the reports are assembled from marker files on disk, a module analyzed in an earlier run still contributed its stale markers, so a single-module run could produce a report listing modules it never ran. I ended up taking both of the options you offered in that thread: the header stays, and finalizeStyleViolations / finalizeAnalysisViolations and all four analyzer finalizedBy registrations are gone. Only the aggregate lane writes the Markdown now. One consequence worth knowing: --continue is now required to get a report when an analyzer fails, which the documented commands and every workflow already pass, and that is documented in AGENTS.md and the skill.

Other notable changes

  • PMD and SpotBugs enablement moved out of gradle.properties entirely to a per-project grailsCodeAnalysis { pmdEnabled = true } opt-in. The root aggregator derives the enabled set from applied plugins, so adding a module is a one-line change in that module and nothing central is edited. The -P properties remain as overrides. One caveat: analyzer tasks are now registered during afterEvaluate, so per-task customization must be wrapped in afterEvaluate { }. That is documented.
  • Configuration avoidance restored via configureEach and collection-based edges; the cleanup task no longer wipes every module's reports or defeats up-to-date checks.
  • SnakeYAML now uses a managed snakeyamlVersion=2.6 property instead of the hardcoded 2.4.
  • The AGENTS.md skill-index requirement is dropped, so this no longer collides with Improve agent guidance: skill discovery, module skills, review workflow #15977. Either an explicit index or directory-based discovery passes. Whichever of us rebases second: the front-matter rules still require string name, description and license on every canonical SKILL.md.

Verification

../gradlew :build-logic:test from build-logic: 85 tests, 0 failures, 0 errors, 0 skipped, up from 77. That includes the SpotBugs and Checkstyle end-to-end paths you called out as missing, the style-side missing-report case, aggregate report immutability under a direct analyzer run, and the CI wiring in both directions. Fixtures no longer hardcode tool versions. ./gradlew rat passes.

No runtime module behavior changes, so CI will re-verify the full suite on the updated branch.

@jamesfredley

Copy link
Copy Markdown
Contributor Author

@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.

@jdaugherty

Copy link
Copy Markdown
Contributor

@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
@jamesfredley

Copy link
Copy Markdown
Contributor Author

Thanks for the detailed review. Addressed on chore/automate-project-conventions:

7a0aceb - main review feedback

  • First-party actions/* and apache/grails-github-actions/* back on version/branch refs; third-party stay SHA-pinned
  • PMD opt-in via per-project grailsCodeAnalysis extension (no central project-name list)
  • Lazy aggregation (configureEach), aggregate-only cleanup/Markdown, no direct-task report clobbering
  • Validator UTF-8, i18n grails-app/i18n/**/*.properties, worktree hygiene, skill-discovery compatibility with Improve agent guidance: skill discovery, module skills, review workflow #15977
  • Release packaging: symlink-safe timestamps, agent guidance kept in source zip, SnakeYAML managed at 2.6
  • Expanded TestKit coverage and CI wiring for conventions + advisory SpotBugs

9a8fbee - source-root marker

  • Keep .asf.yaml in the signed source ZIP (it is the findRootGrailsCoreDir marker); RAT-only exclude
  • Terminate root search at the filesystem root; GradleUtilsSpec covers both paths

Open inline threads have been resolved against the current head. Happy to follow up on anything still unclear.

@jamesfredley

Copy link
Copy Markdown
Contributor Author

@jdaugherty I am fine leaving the Apache Grails actions unpinned.

@testlens-app

testlens-app Bot commented Jul 29, 2026

Copy link
Copy Markdown

🚨 TestLens detected 12 failed tests 🚨

Here is what you can do:

  1. Inspect the test failures carefully.
  2. If you are convinced that some of the tests are flaky, you can mute them below.
  3. Finally, trigger a rerun by checking the rerun checkbox.

Test Summary

CI - Groovy Joint Validation Build / build_grails > :grails-core:test

Test Runs Flakiness
GrailsBootstrapRegistryInitializerSpec > defaults logFullStackTraceOnFilter to true on the promoted DefaultStackTraceFilterer 0% 🟢
GrailsUtilStackFiltererSpec > installed DefaultStackTraceFilterer emits Full Stack Trace by default 0% 🟢

CI / Build Grails-Core (macos-latest, 21) > :grails-core:test

Test Runs Flakiness
GrailsBootstrapRegistryInitializerSpec > defaults logFullStackTraceOnFilter to true on the promoted DefaultStackTraceFilterer 0% 🟢
GrailsUtilStackFiltererSpec > installed DefaultStackTraceFilterer emits Full Stack Trace by default 0% 🟢

CI / Build Grails-Core (ubuntu-latest, 21) > :grails-core:test

Test Runs Flakiness
GrailsBootstrapRegistryInitializerSpec > defaults logFullStackTraceOnFilter to true on the promoted DefaultStackTraceFilterer 0% 🟢
GrailsUtilStackFiltererSpec > installed DefaultStackTraceFilterer emits Full Stack Trace by default 0% 🟢

CI / Build Grails-Core (ubuntu-latest, 25) > :grails-core:test

Test Runs Flakiness
GrailsBootstrapRegistryInitializerSpec > defaults logFullStackTraceOnFilter to true on the promoted DefaultStackTraceFilterer 0% 🟢
GrailsUtilStackFiltererSpec > installed DefaultStackTraceFilterer emits Full Stack Trace by default 0% 🟢

CI / Build Grails-Core (windows-latest, 25) > :grails-core:test

Test Runs Flakiness
GrailsBootstrapRegistryInitializerSpec > defaults logFullStackTraceOnFilter to true on the promoted DefaultStackTraceFilterer 0% 🟢
GrailsUtilStackFiltererSpec > installed DefaultStackTraceFilterer emits Full Stack Trace by default 0% 🟢

CI / Build Grails-Core Rerunning all Tasks (ubuntu-latest, 21) > :grails-core:test

Test Runs Flakiness
GrailsBootstrapRegistryInitializerSpec > defaults logFullStackTraceOnFilter to true on the promoted DefaultStackTraceFilterer 0% 🟢
GrailsUtilStackFiltererSpec > installed DefaultStackTraceFilterer emits Full Stack Trace by default 0% 🟢

🏷️ Commit: 9a8fbee
▶️ Tests: 36410 executed
⚪️ Checks: 59/59 completed

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))
Condition not satisfied:

baos.toString().contains(StackTraceFilterer.FULL_STACK_TRACE_MESSAGE)
|    |          |        |                  |
|    ""         false    |                  Full Stack Trace:
|                        interface org.grails.exceptions.reporting.StackTraceFilterer
java.io.ByteArrayOutputStream@60b65a4a

	at org.apache.grails.core.GrailsBootstrapRegistryInitializerSpec.defaults logFullStackTraceOnFilter to true on the promoted DefaultStackTraceFilterer(GrailsBootstrapRegistryInitializerSpec.groovy:257)
GrailsUtilStackFiltererSpec > installed DefaultStackTraceFilterer emits Full Stack Trace by default (:grails-core:test in CI / Build Grails-Core (macos-latest, 21))
Condition not satisfied:

baos.toString().contains(StackTraceFilterer.FULL_STACK_TRACE_MESSAGE)
|    |          |        |                  |
|    ""         false    |                  Full Stack Trace:
|                        interface org.grails.exceptions.reporting.StackTraceFilterer
java.io.ByteArrayOutputStream@6acc72c7

	at grails.util.GrailsUtilStackFiltererSpec.installed DefaultStackTraceFilterer emits Full Stack Trace by default(GrailsUtilStackFiltererSpec.groovy:143)
GrailsBootstrapRegistryInitializerSpec > defaults logFullStackTraceOnFilter to true on the promoted DefaultStackTraceFilterer (:grails-core:test in CI / Build Grails-Core (ubuntu-latest, 21))
Condition not satisfied:

baos.toString().contains(StackTraceFilterer.FULL_STACK_TRACE_MESSAGE)
|    |          |        |                  |
|    ""         false    |                  Full Stack Trace:
|                        interface org.grails.exceptions.reporting.StackTraceFilterer
java.io.ByteArrayOutputStream@68d9a4dd

	at org.apache.grails.core.GrailsBootstrapRegistryInitializerSpec.defaults logFullStackTraceOnFilter to true on the promoted DefaultStackTraceFilterer(GrailsBootstrapRegistryInitializerSpec.groovy:257)
GrailsUtilStackFiltererSpec > installed DefaultStackTraceFilterer emits Full Stack Trace by default (:grails-core:test in CI / Build Grails-Core (ubuntu-latest, 21))
Condition not satisfied:

baos.toString().contains(StackTraceFilterer.FULL_STACK_TRACE_MESSAGE)
|    |          |        |                  |
|    ""         false    |                  Full Stack Trace:
|                        interface org.grails.exceptions.reporting.StackTraceFilterer
java.io.ByteArrayOutputStream@eb52295

	at grails.util.GrailsUtilStackFiltererSpec.installed DefaultStackTraceFilterer emits Full Stack Trace by default(GrailsUtilStackFiltererSpec.groovy:143)
GrailsBootstrapRegistryInitializerSpec > defaults logFullStackTraceOnFilter to true on the promoted DefaultStackTraceFilterer (:grails-core:test in CI / Build Grails-Core (ubuntu-latest, 25))
Condition not satisfied:

baos.toString().contains(StackTraceFilterer.FULL_STACK_TRACE_MESSAGE)
|    |          |        |                  |
|    ""         false    |                  Full Stack Trace:
|                        interface org.grails.exceptions.reporting.StackTraceFilterer
java.io.ByteArrayOutputStream@5a7a1c19

	at org.apache.grails.core.GrailsBootstrapRegistryInitializerSpec.defaults logFullStackTraceOnFilter to true on the promoted DefaultStackTraceFilterer(GrailsBootstrapRegistryInitializerSpec.groovy:257)
GrailsUtilStackFiltererSpec > installed DefaultStackTraceFilterer emits Full Stack Trace by default (:grails-core:test in CI / Build Grails-Core (ubuntu-latest, 25))
Condition not satisfied:

baos.toString().contains(StackTraceFilterer.FULL_STACK_TRACE_MESSAGE)
|    |          |        |                  |
|    ""         false    |                  Full Stack Trace:
|                        interface org.grails.exceptions.reporting.StackTraceFilterer
java.io.ByteArrayOutputStream@5e7558ef

	at grails.util.GrailsUtilStackFiltererSpec.installed DefaultStackTraceFilterer emits Full Stack Trace by default(GrailsUtilStackFiltererSpec.groovy:143)
GrailsBootstrapRegistryInitializerSpec > defaults logFullStackTraceOnFilter to true on the promoted DefaultStackTraceFilterer (:grails-core:test in CI / Build Grails-Core (windows-latest, 25))
Condition not satisfied:

baos.toString().contains(StackTraceFilterer.FULL_STACK_TRACE_MESSAGE)
|    |          |        |                  |
|    ""         false    |                  Full Stack Trace:
|                        interface org.grails.exceptions.reporting.StackTraceFilterer
java.io.ByteArrayOutputStream@654a4b51

	at org.apache.grails.core.GrailsBootstrapRegistryInitializerSpec.defaults logFullStackTraceOnFilter to true on the promoted DefaultStackTraceFilterer(GrailsBootstrapRegistryInitializerSpec.groovy:257)
GrailsUtilStackFiltererSpec > installed DefaultStackTraceFilterer emits Full Stack Trace by default (:grails-core:test in CI / Build Grails-Core (windows-latest, 25))
Condition not satisfied:

baos.toString().contains(StackTraceFilterer.FULL_STACK_TRACE_MESSAGE)
|    |          |        |                  |
|    ""         false    |                  Full Stack Trace:
|                        interface org.grails.exceptions.reporting.StackTraceFilterer
java.io.ByteArrayOutputStream@6ed68a2e

	at grails.util.GrailsUtilStackFiltererSpec.installed DefaultStackTraceFilterer emits Full Stack Trace by default(GrailsUtilStackFiltererSpec.groovy:143)
GrailsBootstrapRegistryInitializerSpec > defaults logFullStackTraceOnFilter to true on the promoted DefaultStackTraceFilterer (:grails-core:test in CI / Build Grails-Core Rerunning all Tasks (ubuntu-latest, 21))
Condition not satisfied:

baos.toString().contains(StackTraceFilterer.FULL_STACK_TRACE_MESSAGE)
|    |          |        |                  |
|    ""         false    |                  Full Stack Trace:
|                        interface org.grails.exceptions.reporting.StackTraceFilterer
java.io.ByteArrayOutputStream@5574f42b

	at org.apache.grails.core.GrailsBootstrapRegistryInitializerSpec.defaults logFullStackTraceOnFilter to true on the promoted DefaultStackTraceFilterer(GrailsBootstrapRegistryInitializerSpec.groovy:257)
GrailsUtilStackFiltererSpec > installed DefaultStackTraceFilterer emits Full Stack Trace by default (:grails-core:test in CI / Build Grails-Core Rerunning all Tasks (ubuntu-latest, 21))
Condition not satisfied:

baos.toString().contains(StackTraceFilterer.FULL_STACK_TRACE_MESSAGE)
|    |          |        |                  |
|    ""         false    |                  Full Stack Trace:
|                        interface org.grails.exceptions.reporting.StackTraceFilterer
java.io.ByteArrayOutputStream@46a0c2df

	at grails.util.GrailsUtilStackFiltererSpec.installed DefaultStackTraceFilterer emits Full Stack Trace by default(GrailsUtilStackFiltererSpec.groovy:143)

Muted Tests

Select tests to mute in this pull request:

  • GrailsBootstrapRegistryInitializerSpec > defaults logFullStackTraceOnFilter to true on the promoted DefaultStackTraceFilterer
  • GrailsUtilStackFiltererSpec > installed DefaultStackTraceFilterer emits Full Stack Trace by default

Reuse successful test results:

  • ♻️ Only rerun the tests that failed or were muted before

Click the checkbox to trigger a rerun:

  • Rerun jobs

Learn more about TestLens at testlens.app.

@borinquenkid

Copy link
Copy Markdown
Member

The TestLens failures for GrailsUtilStackFiltererSpec > installed DefaultStackTraceFilterer emits Full Stack Trace by default and GrailsBootstrapRegistryInitializerSpec > defaults logFullStackTraceOnFilter to true on the promoted DefaultStackTraceFilterer are a pre-existing bug on 8.0.x, unrelated to this PR's changes -- reproducible on plain 8.0.x today. DefaultStackTraceFilterer.STACK_LOG routes through a jcl-over-slf4j commons-logging binding, so the tests' System.setErr() capture never observes the emitted message.

Fix: #16067 (replaces the System.err capture with a Logback appender attached directly to the logger). Once that merges, rebasing onto 8.0.x should clear this failure here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Todo

Development

Successfully merging this pull request may close these issues.

Stabilize SpotBugs task wiring and detector execution

5 participants