9.2 KiB
Release Hygiene Refactoring Design
Date: 2026-08-01 Status: approved by the user's instruction to apply the preceding review Scope: release-blocking architecture test, Gradle wrapper supply-chain integrity, Docker build configuration inputs, SpotBugs analysis completeness, and the observed Gradle 10 deprecation
Context
The repository-wide review found that the 19-leaf Clean Architecture dependency model is healthy, but the release surface is not green:
:app-bootstrap:sampleOffTestfails because a whole-composition Object Storage ArchUnit rule is evaluated on the intentionally sample-free classpath withallowEmptyShould(false).- the two Dockerfiles run Gradle before copying configuration-time registry inputs, while the root
build also requires a Git checkout during configuration even though the Docker context excludes
.git; gradle-wrapper.propertiesselects Gradle 9.0.0 while the checked-in wrapper JAR is from another official Gradle release, and the distribution checksum is absent;- clean SpotBugs analysis reports missing Spring Session, Micrometer Context Propagation, and protobuf classes;
- a root task calls
Task.projectduring execution, which is deprecated and scheduled to fail in Gradle 10.
This design deliberately closes those release-hygiene defects before changing idempotency, outbox, security, or sample data behavior. Each later subsystem gets a separate design and plan so that a reviewer can accept or revert it independently.
Considered Approaches
Approach A: weaken the existing global gates
Set ArchUnit rules to allow empty matches, ignore SpotBugs missing-class messages, and make Docker configuration registries optional. This is the smallest diff, but it makes the architecture and static-analysis gates less trustworthy. Rejected.
Approach B: patch each symptom in place
Condition the ArchUnit rule on a sample flag, copy only the two currently missing registry files, and add the three currently missing SpotBugs JARs manually. This would pass today's cases but would recur whenever another leaf, registry, source set, or dependency is added. Rejected because it duplicates ownership knowledge.
Approach C: align ownership and derive inputs from the owning model
Move the leaf-specific architecture rule to the Object Storage leaf, keep root tests responsible
for cross-leaf registration, treat config/** as a declared Docker configuration input, move Git
evidence checks to the evidence task execution phase, align the wrapper artifacts to one version,
and derive SpotBugs auxiliary inputs from each analyzed source set's runtime classpath. Selected.
Architecture Test Ownership
adapter-outbound-objectstorage owns rules about the public types of its production adapter methods.
The rule moves out of app-bootstrap and runs in the Object Storage module's normal test suite.
It remains strict: the Object Storage module must contain matching production classes and the rule
must not globally allow an empty should clause.
app-bootstrap continues to own cross-module rules. Its sample-off suite verifies that production
composition works without sample-portfolio; it does not require sample-only leaves to be present.
The existing module registry and dependency verification remain the SSOT for leaf coverage.
Gradle Wrapper Integrity
Gradle 9.0.0 remains the selected version for this refactoring. The wrapper scripts, properties, and JAR are regenerated from Gradle 9.0.0 in a trusted environment. The official 9.0.0 binary distribution SHA-256 is recorded as:
8fad3d78296ca518113f3d29016617c7f9367dc005f932bd9d93bf45ba46072b
The wrapper properties are one exact ordered eight-line byte contract, preventing Java Properties duplicate-key, separator, escape, and continuation semantics from overriding the reviewed values. The complete six-file workflow path set and every workflow's SHA-256 are embedded as a reviewed byte lock in the verifier. This is the primary completeness boundary: YAML has aliases, encoded keys, duplicate-key overrides, custom shells, and other equivalent representations that a partial Bash parser cannot safely model. Any workflow addition, removal, rename, symlink replacement, or byte change fails until the complete workflow diff is intentionally reviewed and the sorted lock is refreshed in the same change.
The restricted block-style workflow grammar remains defense in depth and supplies actionable
diagnostics for ordinary drift. Every Gradle-running job uses an unconditional validation step
with a stable ID and the action pinned by commit SHA. Checkout and validation precede every Gradle
invocation, not only the first; a cleanup/sanitizer step that intentionally uses always() also
requires the validation step's successful outcome. This is consistent with the repository's
existing pinned actions/setup-java policy and prevents wrapper failure from being bypassed by
step conditions.
Docker Configuration Contract
Both Docker build dependency-cache stages preserve the repository layout with WORKDIR /build/src
and copy the complete config/** tree before invoking Gradle. The parent /build is therefore the
repository root expected by registry source_path: src/** entries. This is intentional: Gradle
configuration registries and their repository-relative path base are build inputs, while the
registry's exact internal file list may evolve.
Git revision validation no longer runs unconditionally while the build script is being configured.
A root-owned resolver is invoked once from each root evidence action or leaf evidence test's
root-suite completion action; eager scalar evidence properties are removed. Only evidence-producing
tasks resolve the checkout revision during their execution. Docker builds provide
-PgitRevision=<40 lowercase hex> and do not copy .git into the image context.
The boot JAR path is obtained from Gradle's archive output contract rather than selecting the first filesystem match. The final images retain the existing digest-pinned base image, non-root user, read-only root filesystem, and JRE-only runtime.
SpotBugs and Gradle 10 Compatibility
Every SpotBugs task analyzes a named source set and receives that source set's runtime classpath as its auxiliary analysis classpath, excluding its own compiled output. Custom test source sets are covered by the same rule. No production dependency scope is widened merely to silence SpotBugs.
Missing-analysis-class output is treated as a gate failure. The clean gate must produce zero
classes needed for analysis were missing messages.
The observed Gradle 10 deprecation is removed by capturing the application-core project during
configuration instead of calling Task.project from the task action. The dependency-purity gate
still traverses that project's configurations during execution, so it explicitly opts out of the
configuration cache rather than claiming serializable declared inputs it does not have.
Error Handling and Failure Semantics
- sample-off fails only for a real production composition or architecture violation;
- an empty Object Storage rule in its owning module is a test failure;
- a wrapper JAR or distribution checksum mismatch fails before Gradle build logic executes in CI;
- missing Docker configuration input fails with a named build-contract test rather than an opaque settings error;
- invalid or absent
gitRevisionfails only an evidence task that requires it; - SpotBugs missing classes fail static analysis instead of producing a successful partial report.
Verification Design
The implementation follows red-green-refactor. Each behavior has a regression test or executable contract that fails before the production/configuration change:
- reproduce
sampleOffTestfailure, then add an owner-module architecture test and remove the misplaced global rule; - add wrapper property and workflow contract assertions before regenerating the wrapper;
- extend Docker contract tests so a cache-stage Gradle configuration fixture requires
config/**and accepts an attestedgitRevisionwithout.git; - add Gradle build-contract coverage for source-set-derived SpotBugs auxiliary classpaths, the
removed execution-time
Task.projectaccess, and the explicit configuration-cache opt-out; - run focused gates, then the clean repository-wide gate and gate-matrix script.
Non-Goals
- no dependency version upgrade beyond aligning the wrapper to the already selected Gradle 9.0.0;
- no business/domain behavior changes;
- no idempotency, outbox, Poster publication, security, DTO, or database migration changes;
- no broad extraction of the 3,768-line root build script in this phase;
- no agent-created branch, stage, commit, amend, or push.
Decision Summary
- Object Storage-specific ArchUnit rules live with Object Storage.
- Root architecture rules remain strict and cross-module only.
- Gradle stays at 9.0.0 and gains exact wrapper/distribution validation.
- Docker copies
config/**; Git evidence is execution-scoped and supplied bygitRevision. - SpotBugs uses source-set runtime classpaths and fails on missing analysis classes.
- The dependency-purity task avoids execution-time
Task.projectaccess and truthfully declares its configuration-cache incompatibility while it still inspects project configurations.