161 lines
9.2 KiB
Markdown
161 lines
9.2 KiB
Markdown
# 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:sampleOffTest` fails because a whole-composition Object Storage ArchUnit rule is
|
|
evaluated on the intentionally sample-free classpath with `allowEmptyShould(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.properties` selects 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.project` during 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:
|
|
|
|
```text
|
|
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 `gitRevision` fails 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:
|
|
|
|
1. reproduce `sampleOffTest` failure, then add an owner-module architecture test and remove the
|
|
misplaced global rule;
|
|
2. add wrapper property and workflow contract assertions before regenerating the wrapper;
|
|
3. extend Docker contract tests so a cache-stage Gradle configuration fixture requires `config/**`
|
|
and accepts an attested `gitRevision` without `.git`;
|
|
4. add Gradle build-contract coverage for source-set-derived SpotBugs auxiliary classpaths, the
|
|
removed execution-time `Task.project` access, and the explicit configuration-cache opt-out;
|
|
5. 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 by `gitRevision`.
|
|
- SpotBugs uses source-set runtime classpaths and fails on missing analysis classes.
|
|
- The dependency-purity task avoids execution-time `Task.project` access and truthfully declares
|
|
its configuration-cache incompatibility while it still inspects project configurations.
|