Files
clean-architecture-backend-…/docs/superpowers/plans/2026-08-02-warning-zero-build-refactoring.md
T
DongHyeonkaandClaude Opus 5 5f10b791d3 chore: record pre-existing uncommitted repository state
Snapshot of the in-flight state that already existed, identically, in both
this worktree and the main checkout before this session began: the initial
HTTP Client platform implementation (previously untracked), the redis-lab
removal, and the JPA / object-storage / notification integration work.

Kept separate from this session's HTTP Client review response, which lands
in the following commit, so the two bodies of work stay reviewable apart.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 16:48:43 +09:00

22 KiB

Warning-Zero Build Refactoring Implementation Plan

For Codex: REQUIRED SUB-SKILLS: use superpowers:subagent-driven-development for the independent owner-leaf batches, superpowers:test-driven-development for behavior changes, superpowers:systematic-debugging for any failure, and superpowers:verification-before-completion before reporting success.

Goal: Remove the audited compiler/static-analysis/test-output warning debt, preserve the approved legacy compatibility boundaries, and make the blocking build fail on any future warning.

Architecture: Fix behavior in the owning leaf, preserve identity/framework/compatibility seams with the narrowest justified suppressions, migrate deprecated provider APIs in their outbound leaf, then enable root Gradle/CI gates only after all focused tasks are clean. No dependency edge or runtime membership changes are permitted. The 19-leaf registry remains the dependency SSOT.

Tech Stack: Java 21, Spring Boot 4.0.0, Gradle multi-project build, JUnit 5, AssertJ, Mockito, Error Prone, Checkstyle, SpotBugs, Jackson 3.0.2, Lettuce 6.8.1, AWS SDK v2, Testcontainers 2.

Approved design: docs/superpowers/specs/2026-08-02-warning-zero-build-design.md

Repository constraints: The worktree already contains user/P0/P1/P2 changes. Preserve them, never reset or rewrite unrelated files, and do not stage, commit, amend, or push. Agent tasks must edit only their assigned files and report overlaps before proceeding.

Task 1: Freeze warning evidence and add behavior regressions

Owner leaves: adapter-inbound-web, adapter-outbound-notification, sample-portfolio, app-bootstrap

Files:

  • Add: src/adapter/inbound/web/src/test/java/dev/caskeleton/adapter/inbound/web/auth/JwtToAuthenticatedPrincipalConverterTest.java
  • Modify: src/adapter/inbound/web/src/test/java/dev/caskeleton/adapter/inbound/web/conditional/ETagsTest.java
  • Modify: src/adapter/outbound/notification/src/test/java/dev/caskeleton/adapter/outbound/notification/core/RoutingNotifierTest.java
  • Modify: src/sample-portfolio/src/test/java/dev/caskeleton/sample/portfolio/adapter/outbound/repostats/RepoStatsAclMapperTest.java
  • Modify: src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/async/AsyncGracefulShutdownBehaviorTest.java

Steps:

  1. Add Turkish-default-locale regressions for JWT role uppercasing, notification route-key lowercasing, and repository ACL lowercasing. Snapshot Locale.getDefault(), set Locale.forLanguageTag("tr-TR"), and restore it in finally.

  2. Add RED ETag cases for "opaque,tag", weak W/"opaque,tag" inside a mixed list, malformed unclosed quotes, wildcard, blank, stale, and ordinary multiple values.

  3. Add a RED async case proving an exception raised in the submitted action reaches the test through Future.get().

  4. Run the exact focused tests. Confirm the new locale/ETag cases fail for the intended reason; the async change uses the existing FutureReturnValueIgnored compile diagnostic as its RED contract:

    ./gradlew :adapter:inbound:web:test --tests '*JwtToAuthenticatedPrincipalConverterTest' --tests '*ETag*' --console=plain
    ./gradlew :adapter:outbound:notification:test --tests '*RoutingNotifier*' --console=plain
    ./gradlew :sample-portfolio:test --tests '*RepoStatsAclMapper*' --console=plain
    ./gradlew :app-bootstrap:test --tests '*AsyncGracefulShutdownBehaviorTest' --console=plain
    
  5. Do not change production code in this task; retain the behavior-test failures and compile warning as the TDD/static-analysis baseline.

Task 2: Correct locale, ETag, async, cleanup, and host-default behavior

Owner leaves: adapter-inbound-web, adapter-outbound-notification, sample-portfolio, app-bootstrap, application-core, shared-contract, adapter-outbound-fileserver, adapter-outbound-httpclient, adapter-outbound-identifier

Production files:

  • Modify: src/adapter/inbound/web/src/main/java/dev/caskeleton/adapter/inbound/web/auth/JwtToAuthenticatedPrincipalConverter.java
  • Modify: src/adapter/inbound/web/src/main/java/dev/caskeleton/adapter/inbound/web/conditional/ETags.java
  • Modify: src/adapter/outbound/notification/src/main/java/dev/caskeleton/adapter/outbound/notification/core/RoutingNotifier.java
  • Modify: src/sample-portfolio/src/main/java/dev/caskeleton/sample/portfolio/adapter/outbound/repostats/RepoStatsAclMapper.java

Test/mechanical files:

  • Modify the nine audited implicit-charset sites in CursorCodecTest, RedisTrustMaterialProviderTest, OutboundHttpClientTest, HmacUserPrincipalPseudonymizerTest, StreamingResponseBodyAllowedFixture, and IdempotencyExecutorTest.
  • Modify the remaining audited test-only locale sites in JwtDecoderConfigTest, OutboundHttpClientTest, WorkLogReservedIntegrationEventMapperJsonTest, WorkLogIdTest, and TraceParentTest.
  • Modify: src/sample-portfolio/src/test/java/dev/caskeleton/sample/portfolio/domain/worklog/WorkLogTest.java
  • Modify the four outbox cleanup classes under src/app-bootstrap/src/test/java/dev/caskeleton/bootstrap/integration/outbox/.
  • Modify: src/adapter/outbound/fileserver/src/test/java/dev/caskeleton/adapter/outbound/fileserver/FilesystemCsvExportAdapterTest.java

Steps:

  1. Use Locale.ROOT at the three production identifier sites and at audited test comparisons.

  2. Replace ETags delimiter splitting with a quote-aware scanner. Split only on commas outside quoted opaque tags; malformed quoting yields no match. Keep wildcard and weak-tag semantics.

  3. Retain and observe the async Future<?>; unwrap ExecutionException only as required by the test's existing assertion contract.

  4. Replace empty cleanup catches with propagation or IllegalStateException/UncheckedIOException preserving the original cause.

  5. Replace implicit charset calls with StandardCharsets.UTF_8; replace LocalDate.now() test data with the fixed intended date or an explicit UTC clock.

  6. Convert byte-identical readability literals to text blocks and verify the exact expected strings.

  7. Run the focused tests from Task 1 and the affected owner test suites:

    ./gradlew :application-core:test :shared-contract:test :adapter:inbound:web:test \
      :adapter:outbound:notification:test :adapter:outbound:fileserver:test \
      :adapter:outbound:httpclient:test :adapter:outbound:identifier:test \
      :sample-portfolio:test :app-bootstrap:test --console=plain
    

Task 3: Preserve Redis invariants and migrate Lettuce calls

Owner leaf: adapter-outbound-cache-redis

Files:

  • Modify: src/adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/RedisPrimitiveInvocation.java
  • Modify: src/adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/RedisTopologyCommandRuntime.java
  • Modify: src/adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/RedisVersionedSession.java
  • Modify: src/adapter/outbound/cache-redis/src/main/java/dev/caskeleton/adapter/outbound/cache/redis/VersionedRedisSessionStore.java
  • Add: src/adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/RedisPrimitiveInvocationTest.java
  • Add: src/adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/VersionedRedisSessionStoreTest.java
  • Modify: src/adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/LettuceRedisRuntimeServiceTest.java
  • Modify: src/adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/RedisVersionedSessionRepositoryTest.java
  • Modify: src/adapter/outbound/cache-redis/src/test/java/dev/caskeleton/adapter/outbound/cache/redis/RedisPrimitiveRuntimeServiceTest.java

Steps:

  1. Add characterization regressions proving a value-equal descriptor from a different catalog is rejected and the four session array records copy constructor inputs and accessor outputs. These should pass before implementation because they justify preserving the invariants; the compiler warnings are the RED executable contract for the suppression/migration work.

  2. Keep descriptor reference equality and add constructor-only @SuppressWarnings("ReferenceEquality") with an invariant rationale.

  3. Qualify every ambiguous nested ExpectedKind reference with its enclosing record.

  4. Keep Spring Session's <T> T getAttribute(String) signature and add method-only TypeParameterUnusedInFormals suppression.

  5. Preserve defensive copying for the four VersionedRedisSessionStore array records; apply exact ArrayRecordComponent suppressions to those records and the private test fake only.

  6. Convert canonical finite score strings to BigDecimal, build inclusive Lettuce Range values, and use typed zcount and zrangebyscoreWithScores(..., Limit.create(...)) overloads. Extend the runtime proxy test to prove both overloads and their offset/count arguments.

  7. Replace one-shot new SecureRandom() with one static final instance.

  8. Run:

    ./gradlew :adapter:outbound:cache-redis:test --console=plain
    ./gradlew :adapter:outbound:cache-redis:compileJava \
      :adapter:outbound:cache-redis:compileTestJava --rerun-tasks --console=plain
    ./gradlew :adapter:outbound:cache-redis:spotbugsTest --rerun-tasks --console=plain
    

Task 4: Preserve HTTP retry and notification ciphertext invariants

Owner leaves: adapter-outbound-httpclient, adapter-outbound-persistence-jpa

Files:

  • Modify: src/adapter/outbound/httpclient/src/main/java/dev/caskeleton/adapter/outbound/httpclient/OutboundRetryPolicy.java
  • Modify: src/adapter/outbound/httpclient/src/test/groovy/dev/caskeleton/adapter/outbound/httpclient/OutboundRetryPolicySpec.groovy
  • Modify: src/adapter/outbound/persistence-jpa/src/main/java/dev/caskeleton/adapter/outbound/persistence/notification/crypto/NotificationCiphertext.java
  • Modify: src/adapter/outbound/persistence-jpa/src/test/java/dev/caskeleton/adapter/outbound/persistence/notification/crypto/NotificationPayloadCryptoTest.java

Steps:

  1. Add a characterization test using two OutboundRetryPolicy instances on one thread: policy A context must not be visible to policy B, and endCall() must clear the owning context. It should pass before implementation and justifies preserving the instance field; the compile warning is the RED contract.

  2. Keep the instance ThreadLocal; add field-only ThreadLocalUsage suppression with the isolation reason.

  3. Add/strengthen tests proving NotificationCiphertext clones nonce/ciphertext inputs and accessors, compares arrays by content, hashes consistently, and never exposes bytes in toString().

  4. Keep the record API and add exact record-level ArrayRecordComponent suppression.

  5. Run:

    ./gradlew :adapter:outbound:httpclient:test --console=plain
    ./gradlew :adapter:outbound:persistence-jpa:test --console=plain
    

Task 5: Migrate Jackson 3 messaging APIs

Owner leaf: adapter-outbound-messaging

Files:

  • Modify: src/adapter/outbound/messaging/src/main/java/dev/caskeleton/adapter/outbound/messaging/schema/LocalJsonSchemaRegistry.java
  • Modify: src/adapter/outbound/messaging/src/main/java/dev/caskeleton/adapter/outbound/messaging/envelope/DeterministicEnvelopeWriter.java
  • Modify: src/adapter/outbound/messaging/src/test/java/dev/caskeleton/adapter/outbound/messaging/envelope/LocalJsonSchemaRegistryTest.java
  • Modify: src/adapter/outbound/messaging/src/test/java/dev/caskeleton/adapter/outbound/messaging/envelope/JsonSchemaIntegrationEventEncoderTest.java

Steps:

  1. Extend existing tests to freeze text-node validation and canonical envelope bytes.

  2. Replace isTextual()/textValue() with isString()/stringValue().

  3. Replace createGenerator(output) with createGenerator(ObjectWriteContext.empty(), output, JsonEncoding.UTF8).

  4. Run:

    ./gradlew :adapter:outbound:messaging:test --console=plain
    ./gradlew :adapter:outbound:messaging:compileJava --rerun-tasks --console=plain
    

Task 6: Preserve legacy object storage and migrate provider APIs

Owner leaves: application-core, adapter-outbound-objectstorage, sample-portfolio, app-bootstrap architecture tests

Files:

  • Modify: src/application-core/src/main/java/dev/caskeleton/application/storage/ObjectStoragePort.java
  • Modify the six Java files under src/application-core/src/main/java/dev/caskeleton/application/storage/migration/.
  • Modify: src/adapter/outbound/objectstorage/src/main/java/dev/caskeleton/adapter/outbound/objectstorage/FilesystemObjectStorageAdapter.java
  • Modify: src/adapter/outbound/objectstorage/src/main/java/dev/caskeleton/adapter/outbound/objectstorage/S3ObjectStorageAdapter.java
  • Modify: src/adapter/outbound/objectstorage/src/main/java/dev/caskeleton/adapter/outbound/objectstorage/maintenance/LegacyObjectInspector.java
  • Modify: src/adapter/outbound/objectstorage/src/main/java/dev/caskeleton/adapter/outbound/objectstorage/maintenance/LegacyObjectAdoptionService.java
  • Modify: src/sample-portfolio/src/main/java/dev/caskeleton/sample/portfolio/application/poster/UploadPosterImageUseCase.java
  • Modify: src/sample-portfolio/src/main/java/dev/caskeleton/sample/portfolio/application/poster/migration/AdoptLegacyPosterImageUseCase.java
  • Modify: src/sample-portfolio/src/main/java/dev/caskeleton/sample/portfolio/bootstrap/objectstorage/PosterImageApiConfig.java
  • Modify: src/sample-portfolio/src/main/java/dev/caskeleton/sample/portfolio/adapter/inbound/web/controller/LegacyPosterImageController.java
  • Modify: src/sample-portfolio/src/main/java/dev/caskeleton/sample/portfolio/adapter/inbound/web/mapper/PosterWebMapper.java
  • Modify: src/application-core/src/test/java/dev/caskeleton/application/objectstorage/ObjectStorageArchitectureContractTest.java
  • Modify: src/adapter/outbound/objectstorage/src/main/java/dev/caskeleton/adapter/outbound/objectstorage/s3/S3AsyncClientFactory.java
  • Modify: src/adapter/outbound/objectstorage/src/objectStorageMinioFaultTest/java/dev/caskeleton/adapter/outbound/objectstorage/qualification/MinioManagedObjectFaultTest.java
  • Modify: src/adapter/outbound/objectstorage/build.gradle
  • Modify: src/adapter/outbound/objectstorage/gradle.lockfile only if the toxiproxy dependency graph changes.
  • Modify audited URL, Mockito varargs, range parser, text-block, and legacy characterization tests.

Steps:

  1. Add/retain lifecycle tests: ObjectStoragePort, StoredObject, and adapter-owned ObjectStorageSettings remain forRemoval=true; migration types remain deprecated but are no longer forRemoval.

  2. Change the six migration mechanism types plus AdoptLegacyPosterImageUseCase to plain @Deprecated. Add only exact deprecation suppressions at adoption implementation/configuration consumers.

  3. Add only the exact removal suppressions named by the design to legacy implementations, controller/mapper/wiring, characterization classes, and single receipt methods.

  4. Replace AWS RetryPolicy/old equal-jitter API with StandardRetryStrategy, half-jitter exponential backoff, exact max attempts, and retryStrategy(...). Assert normal/throttling configuration in S3AsyncClientFactoryTest.

  5. Keep the existing org.testcontainers:testcontainers-toxiproxy dependency, switch to its Testcontainers 2 package, and use ToxiproxyClient/Proxy against an explicitly exposed proxy port. Preserve cut/restore MinIO semantics; update the leaf lock only if resolution actually changes.

  6. Replace new URL(String) with URI.create(...).toURL().

  7. Replace Mockito's two-value varargs thenReturn with two chained single-value stubs.

  8. Replace test-only range splitting with an asserted single-hyphen boundary; keep fingerprint literal bytes identical when converting to a text block.

  9. Run:

    ./gradlew :application-core:test :adapter:outbound:objectstorage:test \
      :sample-portfolio:test --console=plain
    ./gradlew :adapter:outbound:objectstorage:check --console=plain
    ./gradlew :app-bootstrap:test --tests '*CleanArchitectureTest' --console=plain
    ./gradlew verifyDependencyLocks --console=plain
    
  10. If Docker is available, run the MinIO fault source-set task. If unavailable, record the exact environmental blocker; never suppress its deprecation to claim success.

Task 7: Remove remaining mechanical Error Prone warnings

Owner leaves: application-core, app-bootstrap, sample-portfolio, and the exact test leaves from the audit inventory

Files:

  • Modify: IdempotencyExecutor.java, IdempotencySettings.java, SampleIdempotencySettings.java, and matching tests.
  • Modify: TracingSampleRateResolver.java and TestTaxonomyArchitectureTest.java.
  • Modify: CleanArchitectureTest.java, ManagementActuatorSecurityContractTest.java, ProblemDetailDisabledConfigTest.java, and the serialization violation fixture.
  • Modify: CreateWorkLogOutboxTest.java, WorkLogUseCasesTest.java, and the remaining exact sample test warning locations.

Steps:

  1. Replace five Duration.ofHours(72) sites with Duration.ofDays(3).

  2. Add the missing Javadoc summary and render annotation names as {@code @WebMvcTest}.

  3. Add all 16 missing @Override annotations.

  4. Replace Boolean wrapper comparison with the direct literal/assertion form.

  5. Preserve the forbidden new BigDecimal(double/float) bytecode and add method-only BigDecimalLiteralDouble suppressions with fixture rationale.

  6. Replace the three test-only one-argument splits without changing each grammar: limit-bearing CSV handling, equivalent mapping-path scanning, and exact byte-range parsing.

  7. Run affected owner tests and rerun all compile tasks with Error Prone:

    ./gradlew :application-core:test :app-bootstrap:test :sample-portfolio:test --console=plain
    ./gradlew compileJava compileTestJava --rerun-tasks --console=plain
    

Task 8: Capture Redis lab expected failures and configure clean test JVMs

Files:

  • Modify: infra/redis-lab/test/redis-lab-contract.sh
  • Add: src/gradle/test-jvm-agents.gradle
  • Modify: src/build.gradle

Steps:

  1. Change assert_fails to capture stdout/stderr per invocation, require non-zero status, assert the exact expected diagnostic with no extra lines, and print capture only on mismatch.
  2. Run bash -n infra/redis-lab/test/redis-lab-contract.sh, then run the real Redis lab Gradle/shell contract and verify successful output contains no leaked redis-lab: child diagnostics.
  3. Add a dedicated mockitoAgent configuration per Java test project and a relocatable CommandLineArgumentProvider in src/gradle/test-jvm-agents.gradle. Require exactly one mockito-core jar and emit -javaagent:<absolute jar> plus test-only -Xshare:off.
  4. Apply the script once from the root build and wire every ordinary/custom Test task without changing production JVM arguments.
  5. Run representative Mockito-heavy app-bootstrap, Redis, object-storage, and messaging tests and verify no self-attachment/CDS warning is printed.

Task 9: Enable warning-zero blocking gates

Files:

  • Modify: src/build.gradle
  • Modify: src/app-bootstrap/build.gradle
  • Modify: .github/workflows/ci-quality-gates.yml

Steps:

  1. First run every JavaCompile task with -Xlint:deprecation and -Xlint:unchecked; resolve every remaining diagnostic at the exact source owner.

  2. Add -Werror, -Xlint:deprecation, and -Xlint:unchecked to every leaf JavaCompile task while retaining Error Prone.

  3. Remove root checkstyleTest and spotbugsTest ignoreFailures=true.

  4. Remove app-bootstrap sampleOffTest, functionalTest, and conditionalTransportTest Checkstyle/SpotBugs ignore overrides. Keep only quarantineTest non-blocking.

  5. Add --warning-mode=fail to the blocking quality-gates Gradle invocation.

  6. Run:

    ./gradlew checkstyleTest spotbugsTest --rerun-tasks --console=plain
    ./gradlew check --warning-mode=fail --no-daemon --console=plain
    

Task 10: Fresh repository verification, review, and Wiki capture

Files:

  • Modify: docs/superpowers/plans/2026-08-02-warning-zero-build-refactoring.md only if execution evidence exposes a plan correction.
  • Modify external Wiki capture: /home/donghyeon/workspace/ai-tool/llm-wiki-private/raw/branch-notes/main.md and raw/errors/build-success-warning-debt-2026-08-02.md.

Steps:

  1. Run owner-focused tests for every changed leaf.

  2. Run repository verification from src/:

    ./gradlew test --no-daemon --console=plain
    ./gradlew check --no-daemon --console=plain
    ./gradlew build --warning-mode=fail --no-daemon --console=plain
    ./gradlew clean build --warning-mode=all --no-daemon --console=plain
    ./gradlew verifyCleanArchitectureDependencies verifyRuntimeModuleMembership \
      verifyDependencyLocks verifyPublicPathSnapshot verifyEnvKeys \
      --no-daemon --console=plain
    
  3. Verify the gate matrix, wrapper, shell syntax, XML findings/skips, and diff:

    bash .github/scripts/verify-gate-matrix.sh
    bash .github/scripts/verify-gradle-wrapper.sh .
    bash -n infra/redis-lab/test/redis-lab-contract.sh
    git diff --check
    
  4. Scan the fresh build log for warning:, deprecated/unchecked Note:, SpotBugs non-zero output, OpenJDK/CDS warnings, Mockito self-attachment, and leaked expected-negative Redis diagnostics.

  5. Confirm the skipped-test XML inventory is exactly the five approved optional-adapter contract cases and no qualification source set skipped.

  6. Dispatch independent code review over behavior fixes, legacy/provider migrations, and Gradle/test-noise gates. Apply only evidence-backed findings and rerun affected/full gates.

  7. Update the mandatory Wiki branch/error notes with changed files, commands, results, suppression inventory, blocked environment-only qualifications, and evidence grade. Run per-file Wiki lint; retain the known main.md naming-policy conflict without weakening either policy.

  8. Report success only if the clean build is exit zero and the final log is warning/noise clean.