Files
tech-log-backend/docs/superpowers/specs/2026-08-01-release-hygiene-refactoring-design.md
T

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.