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

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

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.